-module(templ_scan).
-export([string/1,string/2,tokens/3,scan/8,format_error/1]).

-import(lists, [reverse/1]).

-include_lib("../include/templ.hrl").


%% format_error(Error)
%%  Return a string describing the error.

format_error({string,Quote,Head}) ->
    ["unterminated " ++ string_thing(Quote) ++
     " starting with " ++ io_lib:write_string(Head,Quote)];
format_error({illegal,Type}) -> io_lib:fwrite("illegal ~w", [Type]);
format_error(char) -> "unterminated character";
format_error(scan) -> "premature end";
format_error({base,Base}) -> io_lib:fwrite("illegal base '~w'", [Base]);
format_error(float) -> "bad float";
%%
format_error(Other) -> io_lib:write(Other).

string_thing($') -> "atom";   %' Stupid Emacs
string_thing(_) -> "string".

%% string(CharList, StartPos)
%%  Takes a list of characters and tries to tokenise them.
%%
%%  Returns:
%%	{ok,[Tok]}
%%	{error,{ErrorPos,?MODULE,What},EndPos}

string(Cs) ->
    string(Cs, 1).

string(Cs, Pos) when is_list(Cs), is_integer(Pos) ->
%     %% Debug replacement line for chopping string into 1-char segments
%     scan([], [], [], Pos, Cs, [], []).
%    scan(Cs, [], [], Pos, [], [], []).
    scan(Cs, [], [], [Pos], [], [], ?LDelim, []).


%% Scan loop.
%%

%% String
done(eof, [], Toks, Pos, eos) ->
    {ok,reverse(Toks),?GetPos(Pos)};
done(eof, Errors, _Toks, Pos, eos) ->
    {Error,ErrorPos} = lists:last(Errors),
    {error,{ErrorPos,?MODULE,Error},?GetPos(Pos)};
done(Cs, Errors, Toks, Pos, eos) ->
    scan(Cs, [], Toks, Pos, eos, Errors, ?LDelim, []);
% %% Debug clause for chopping string into 1-char segments
% done(Cs, Errors, Toks, Pos, [H|T]) ->
%    scan(Cs++[H], [], Toks, Pos, T, Errors);
done(Cs, Errors, Toks, Pos, []) ->
    scan(Cs++eof, [], Toks, Pos, eos, Errors, ?LDelim, []);
%% Stream
done(Cs, [], [{dot,_}|_]=Toks, Pos, io) ->
    {done,{ok,reverse(Toks),Pos},Cs};
done(Cs, [], [_|_], Pos, io) ->
    {done,{error,{Pos,?MODULE,scan},Pos},Cs};
done(Cs, [], [], Pos, eof) ->
    {done,{eof,Pos},Cs};
done(Cs, [], [{dot,_}|_]=Toks, Pos, eof) ->
    {done,{ok,reverse(Toks),Pos},Cs};
done(Cs, [], _Toks, Pos, eof) ->
    {done,{error,{Pos,?MODULE,scan},Pos},Cs};
done(Cs, Errors, _Toks, Pos, io) ->
    {Error,ErrorPos} = lists:last(Errors),
    {done,{error,{ErrorPos,?MODULE,Error},Pos},Cs};
done(Cs, Errors, _Toks, Pos, eof) ->
    {Error,ErrorPos} = lists:last(Errors),
    {done,{error,{ErrorPos,?MODULE,Error},Pos},Cs}.


%% Frontend of scan
%%
scan(Cs, TStack, Toks, PStack, State, Errors, DC, DStack) ->
	scan_delim(Cs, TStack, Toks, PStack, State, Errors, DC, DStack).


%% Scan for a delimiter
%%
scan_delim([C|Cs], TStack, Toks, PStack, State, Errors, [DC|DCs], DStack) 
  when C =:= DC ->
	scan_delim(Cs, TStack, Toks, PStack, State, Errors, DCs, [C|DStack]);
scan_delim([], TStack, Toks, PStack, _State, Errors, [], DStack) ->
    more([], TStack, Toks, PStack, eos, Errors, [], DStack, fun scan_delim/8);
scan_delim([], TStack, Toks, PStack, State, Errors, Delim, DStack) ->
    more([], TStack, Toks, PStack, State, Errors, Delim, DStack, fun scan_text/8);
scan_delim(Cs, TStack, Toks, PStack, State, Errors, [], _DStack) ->
    Pos = reverse(PStack),
    Buf = reverse(TStack),
	templel_scan:scan(Cs, TStack, [{l_delim,Pos},{text,Pos,Buf}|Toks], 
					  ?PushPos(PStack, 1), State, Errors, ?RDelim, []);
scan_delim(Cs, TStack, Toks, PStack, State, Errors, _Delim, DStack) ->
	scan_text(Cs, DStack++TStack, Toks, PStack, State, Errors, ?LDelim, []).


%% Scan for a text
%% 

scan_text([C|Cs], TStack, Toks, PStack, State, Errors, Delim, DStack) ->
    scan(Cs, [C|TStack], Toks, PStack, State, Errors, Delim, DStack);

scan_text([], TStack, Toks, PStack, State, Errors, Delim, DStack) ->
    more([], TStack, Toks, PStack, State, Errors, Delim, DStack, fun scan_text/8);
scan_text(Eof, TStack, Toks, PStack, State, Errors, _Delim, _DStack) ->
    Pos = reverse(PStack),
    Buf = reverse(TStack),
    done(Eof, Errors, [{text,Pos,Buf}|Toks], PStack, State).
